////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2002.
// -------------------------------------------------------------------------
//  File name:   SoundAssetManager.cpp
//  Version:     v1.00
//  Created:     26/4/2005 by Tomas.
//  Compilers:   Visual Studio.NET
//  Description: SoundAssetManager is responsible for memory allocation of
//							 assets and answer queries about their state and priority
// -------------------------------------------------------------------------
//  History:
//  8/6/05   Nick changed createBuffer routine so precache could call it
//                also put warning note in update buffers so precache buffers wouldn't be released
//
////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include "SoundAssetManager.h"
#include "SoundSystem.h" // CSoundSystem should be ISoundSystem
#include "Sound.h"
#include "SoundBufferFmodEx400Event.h"
#include <IRenderer.h>
#include <IAudioDevice.h>
#include <ITimer.h>
#include <CrySizer.h>
#include <StlUtils.h>

CSoundAssetManager::CSoundAssetManager(CSoundSystem *pSoundSystem)
{
	m_pSoundSystem			= pSoundSystem;
	m_bUseMemoryBlock		= false;	// toggles if internal Memory Block should be used or not
	m_MemoryBlockStart	= NULL;	
	m_nMemoryBlockSize	= 0;
	m_nUsedMemorySize		= 0;
	m_fTotalSoundMemory = 0;
	m_fTotalSecondaryMemory = 0;
	m_nUsingOldFMODStatsCounter = RENEW_FMOD_STATS_LIMIT;
	m_fBudgetRatio = 1.0f;
	m_fMainThreshold = 0.0f;
	m_fSecondaryThreshold = 0.0f;
	m_nLastSoundBuffersIndex = 0;
	m_bPoolsTooFull = false;

	m_tUpdate.SetMilliSeconds(0);
	m_nBufferCommandStress = 1;
	m_nBufferCommandPosition = 0;
}

CSoundAssetManager::~CSoundAssetManager(void)
{
	ShutDown();
}

//////////////////////////////////////////////////////////////////////////
// Management
//////////////////////////////////////////////////////////////////////////

// frees all SoundBuffers, deletes all data;
bool CSoundAssetManager::ShutDown()
{
	//SoundBufferPropsMapItor It = m_soundBuffers.begin();
	//while (It!=m_soundBuffers.end())
	//{			
	//	CSoundBuffer *pBuf = It->second;
	//	//if (pBuf->Loaded())
	//	if (pBuf)
	//	{
	//		RemoveSoundBuffer(pBuf->GetProps());
	//	}
	//	else
	//		++It;
	//} //it

	Reset();

	return (true);
}

void CSoundAssetManager::Reset()
{
	stl::free_container(m_lockedResources);
	m_soundBuffers.clearAndFreeMemory();

	//assert(m_BufferCommands.size() == 0);

	stl::free_container(m_BufferCommands);
	m_nUsedMemorySize = 0;

	// delete the old Memory Block
	if (m_bUseMemoryBlock)
	{
		delete[] (char*)m_MemoryBlockStart;
		m_MemoryBlockStart	= NULL;
		m_nMemoryBlockSize	= 0;
		m_bUseMemoryBlock		= false;
	}

	m_fTotalSoundMemory = 0;
	m_fTotalSecondaryMemory = 0;
	m_fMainThreshold = 0.0f;
	m_fSecondaryThreshold = 0.0f;
	m_nLastSoundBuffersIndex = 0;
}

// Returns current Memory Usage
unsigned int CSoundAssetManager::GetMemUsage()
{
	uint32 nSize = 0;
	SoundBufferCRCMapItorConst ItEnd = m_soundBuffers.end();
	for (SoundBufferCRCMapItorConst It=m_soundBuffers.begin(); It!=ItEnd; ++It)
	{			
		CSoundBuffer *pBuf = It->second;
		nSize += pBuf->GetMemoryUsed();
	} //it

	return nSize;
}

// compute memory-consumption, returns rough estimate in MB
void CSoundAssetManager::GetMemoryUsage(class ICrySizer* pSizer) const
{	
	pSizer->AddObject(this, sizeof(*this));
	pSizer->AddObject(m_soundBuffers);			
}
	

unsigned int CSoundAssetManager::GetNumberBuffersLoaded()
{
	return (m_soundBuffers.size());
}

// Cleans up dead buffers, defrags or kicks out low priority Buffers
bool CSoundAssetManager::Update(bool const bIgnoreTimers /* = false */)
{
	FUNCTION_PROFILER(gEnv->pSystem, PROFILE_SOUND);

	if (m_pSoundSystem->IsEnabled())
	{
		size_t nBufferCommands = m_BufferCommands.size();

		if (nBufferCommands)
		{
			FRAME_PROFILER("Sound buffer unloads", GetISystem(), PROFILE_SOUND);

			size_t absCmdIdx = m_nBufferCommandPosition < nBufferCommands ? m_nBufferCommandPosition : 0;

			for (int cmdsToProcess = static_cast<int>(m_nBufferCommandStress); cmdsToProcess > 0 && absCmdIdx < nBufferCommands;)
			{
				SBufferCommand& cmd      = m_BufferCommands[absCmdIdx];
				CSoundBuffer*	const pBuf = cmd.pBuffer;
				bool bCommandCompleted   = false;

				if(cmd.bRemoveBuffer)
				{
					// safety check if the buffer  has a bad CSound reference
					int nNumReferencedSounds = 0;
					ptParamINT32 TempParam(nNumReferencedSounds);
					pBuf->GetParam(apNUMREFERENCEDSOUNDS, &TempParam);
					TempParam.GetValue(nNumReferencedSounds);

					if (nNumReferencedSounds == 0)
					{
						ISoundBuffer::UnloadDataIncrementalResult result = pBuf->UnloadDataIncremental(sbUNLOAD_ALL_DATA, cmd.nBegin, static_cast<size_t>(cmdsToProcess));

						cmd.nBegin += result.processed;
						cmdsToProcess -= static_cast<int>(result.processed);

						if (result.remaining == 0)
							bCommandCompleted = true;	

						// refcount is 2 because of m_ToBeRemoved and m_soundBuffers
						if (pBuf->m_nRef == 2)
						{
							SSoundBufferProps const oSoundBufferProps(*(pBuf->GetProps()));
							CCryNameCRC const oBufferCRC(oSoundBufferProps.sName);

							if (!RemoveSoundBufferCRC(oBufferCRC))
							{
								if (m_pSoundSystem->g_nDebugSound == SOUNDSYSTEM_DEBUG_DETAIL)
									m_pSoundSystem->Log(eSLT_Warning, "<Sound> AssetManager: Trying to remove sound [%s] NOT FOUND!!!", oSoundBufferProps.sName.c_str());
							}
						}
					}
					else
					{
						bCommandCompleted = true; // lets stop deleting this buffer because of file references
					}
				}
				else
				{
					ISoundBuffer::UnloadDataIncrementalResult result = pBuf->UnloadDataIncremental(cmd.eUnloadOption, cmd.nBegin, static_cast<size_t>(cmdsToProcess));

					cmd.nBegin        += result.processed;
					cmdsToProcess     -= static_cast<int>(result.processed);
					bCommandCompleted = result.remaining == 0;
				}

				if (bCommandCompleted)
				{
					m_BufferCommands.erase(m_BufferCommands.begin() + absCmdIdx);
					--nBufferCommands;
				}
				else
				{
					++absCmdIdx;
				}
			}

			m_nBufferCommandPosition = absCmdIdx;
		}

		CTimeValue const oFrameStartTime = gEnv->pTimer->GetFrameStartTime();
		int64 const nMS                  = (oFrameStartTime - m_tUpdate).GetMilliSecondsAsInt64();
		const bool bFullUpdate           = abs(nMS) > UPDATE_SOUND_ASSET_MANAGER_IN_MS;

		if (bFullUpdate || m_bPoolsTooFull || bIgnoreTimers)
		{
			m_tUpdate = oFrameStartTime;

			// Lets see how much memory is used and if the cache is too full, remove some buffers.
			if (m_nUsingOldFMODStatsCounter == RENEW_FMOD_STATS_LIMIT)
			{
				size_t const nPrimaryPoolSize = g_MemoryPoolPrimary.MemSize();
				size_t const nPrimaryPoolFree = g_MemoryPoolPrimary.MemFree();
				size_t const nPrimaryPoolUsedSize = nPrimaryPoolSize - nPrimaryPoolFree;
				const float fPoolPrimaryRatio = nPrimaryPoolSize ? nPrimaryPoolUsedSize/(float)nPrimaryPoolSize : 0.0f;

#ifdef PROVIDE_SECONDARY_POOL
				size_t const nSecondaryPoolSize = g_MemoryPoolSoundSecondary.MemSize();
				size_t const nSecondaryPoolUsedSize = nSecondaryPoolSize - g_MemoryPoolSoundSecondary.MemFree();
				const float fPoolSecondaryRatio = nSecondaryPoolSize ? nSecondaryPoolUsedSize/(float)nSecondaryPoolSize : 0.0f;

				// As this gets updated only once every UPDATE_SOUND_ASSET_MANAGER_IN_MS * RENEW_FMOD_STATS_LIMIT ms keep this result in a member variable.
				m_bPoolsTooFull =	fPoolPrimaryRatio > m_pSoundSystem->g_fMemoryPoolSoundPrimaryRatio || fPoolSecondaryRatio	> m_pSoundSystem->g_fMemoryPoolSoundSecondaryRatio;
#else
				// As this gets updated only once every UPDATE_SOUND_ASSET_MANAGER_IN_MS * RENEW_FMOD_STATS_LIMIT ms keep this result in a member variable.
				m_bPoolsTooFull =	fPoolPrimaryRatio > m_pSoundSystem->g_fMemoryPoolSoundPrimaryRatio;
#endif
				// At 50% pool fill rate we start manipulating the budget ratio but don't let it reach 0.
        m_fBudgetRatio = max(0.2f, min(1.0f, nPrimaryPoolFree / static_cast<float>(nPrimaryPoolSize >> 1)));
				
				m_nUsingOldFMODStatsCounter = 0;
			}
			else
			{
				++m_nUsingOldFMODStatsCounter;
			}

			size_t const nSoundBuffersCount = m_soundBuffers.size();

			if (nSoundBuffersCount)
			{
				FRAME_PROFILER("Sound buffer crc loop", GetISystem(), PROFILE_SOUND);

				int nSoundBufferItCount = (int)cry_ceilf((gEnv->pTimer->GetFrameTime()/1.0f) * (float)nSoundBuffersCount);	

				// make sure we at least process one
				nSoundBufferItCount = max(nSoundBufferItCount, 1);

				SoundBufferCRCMapItor				It		= m_soundBuffers.begin();
				SoundBufferCRCMapItorConst	ItEnd	= m_soundBuffers.end();

				// continue where we stopped last time if index is within the range
				if (nSoundBuffersCount > m_nLastSoundBuffersIndex)
					std::advance(It, m_nLastSoundBuffersIndex);
				else
					m_nLastSoundBuffersIndex = 0;

				for (int i=0; i<nSoundBufferItCount; ++i)
				{
					CSoundBuffer*	const pBuf = It->second;

					if (pBuf) 
					{
						FRAME_PROFILER("Sound buffer crc item", GetISystem(), PROFILE_SOUND);

						// only continue with FSB Buffers when no projects holds a reference
						if (!(pBuf->GetProps()->eBufferType == btSOUNDFSB && pBuf->m_nRef > 1))
						{
							// pBuf is valid
							if (pBuf->LoadFailure() && !pBuf->Loading())
								AddBufferCommand(SBufferCommand(pBuf, sbUNLOAD_NONE, true));
							else
							{
								float const fBufTime = pBuf->GetTimer().GetSeconds();
								float const fNowTime = oFrameStartTime.GetSeconds();
								float const fDiff    = fNowTime - fBufTime;

								// Try to unload data + entire buffer if this buffer hasn't been touched for more than 10 seconds or it has been flagged to immediately unload.
								if (m_bPoolsTooFull || bIgnoreTimers || fDiff > (10.0f * m_fBudgetRatio) || pBuf->GetProps()->nPrecacheFlags & FLAG_SOUND_PRECACHE_UNLOAD_NOW)
								{
									++pBuf->GetInfo()->nTimedOut;

									// Reset the time only on still loading buffers.
									if (pBuf->Loading())
										pBuf->UpdateTimer(oFrameStartTime);

									float const fNeeded = AssetStillNeeded(pBuf); // Calls the evaluation Algorithm

									if (m_bPoolsTooFull || bIgnoreTimers || fNeeded < 1.0f)
									{
										// Immediately remove buffers which hold no data and are not loading
										if (bIgnoreTimers || (!pBuf->Loaded(false) && !pBuf->Loading()))
										{
											AddBufferCommand(SBufferCommand(pBuf, sbUNLOAD_NONE, true));
										}
										else
										{
											// No need to carry on if we're not allowed to unload data.
											// TODO: This can be applied to the entire data unloading code!
											if (m_pSoundSystem->g_nUnloadData != 0)
											{
												if ((pBuf->GetProps()->nPrecacheFlags & FLAG_SOUND_PRECACHE_UNLOAD_NOW) != 0)
												{
													if (m_pSoundSystem->g_nDebugSound == SOUNDSYSTEM_DEBUG_DETAIL)
														m_pSoundSystem->Log(eSLT_Message, "<Sound> unloading (now) data on: %s", pBuf->GetName());

													AddBufferCommand(SBufferCommand(pBuf, sbUNLOAD_ALL_DATA, false));
													pBuf->GetProps()->nPrecacheFlags = 0; // reset the Precache flags to prevent endless unloading
												}

												// If this buffer hasn't been touched for 15 seconds.
												if (m_bPoolsTooFull || bIgnoreTimers || fDiff > (15.0f * m_fBudgetRatio))
												{
													if (m_pSoundSystem->g_nDebugSound == SOUNDSYSTEM_DEBUG_DETAIL)
														m_pSoundSystem->Log(eSLT_Message, "<Sound> unloading data on: %s", pBuf->GetName());

													if (fDiff > (20.0f * m_fBudgetRatio))
													{
														// Remove all the data from buffer that haven't been touched for more than 20 seconds
														AddBufferCommand(SBufferCommand(pBuf, sbUNLOAD_ALL_DATA, false));
														pBuf->GetProps()->nPrecacheFlags = 0;
													}
													else if (!pBuf->m_bFreeingUnneededData)
													{
														// Prevent re-adding before this buffer gets finally removed.
														pBuf->m_bFreeingUnneededData = true;

														// If this buffer hasn't been touched for at least 10 seconds try to unload data partially
														AddBufferCommand(SBufferCommand(pBuf, sbUNLOAD_UNNEEDED_DATA, false));
													}
												}
											}
										}
									}
								}
							}
						}
					}

					// next unprocessed element
					++It;
					++m_nLastSoundBuffersIndex;

					// if current It is the last sound buffer then reset It to begin
					if (It == ItEnd)
					{
						It = m_soundBuffers.begin();
						m_nLastSoundBuffersIndex = 0;
					}
				}

				float frameTime = gEnv->pTimer->GetFrameTime() * 1000.0f;
				frameTime = max(frameTime, 1.0f);
				float numBufferCommands = static_cast<float>(m_BufferCommands.size());
				float numUpdatesPerFullUpdate = static_cast<float>(UPDATE_SOUND_ASSET_MANAGER_IN_MS) / frameTime;

				m_nBufferCommandStress = std::max<size_t>(
					1,
					static_cast<size_t>(numBufferCommands / numUpdatesPerFullUpdate));
			}
		}
	}

	return true;
}


// writes output to screen in debug
void CSoundAssetManager::DrawInformation(IRenderer* pRenderer, float xpos, float ypos, enumAssetManagerDrawInformation DrawMode)
{
#ifdef INCLUDE_AUDIO_PRODUCTION_CODE
	uint32 nUsedMemInByte = m_nUsedMemorySize;
	int nBufferCount = 0;

	if (!(m_soundBuffers.empty()) && m_nUsedMemorySize == 0)
	{
		SoundBufferCRCMapItorConst ItEnd = m_soundBuffers.end();
		for (SoundBufferCRCMapItorConst It=m_soundBuffers.begin(); It!=ItEnd; ++It)
		{			
			++nBufferCount;
			CSoundBuffer *pBuf = It->second;

			if (pBuf->m_BufferData != NULL)
			{
				nUsedMemInByte += pBuf->GetMemoryUsed();
			}
		}
	}

	float fColor[4]         = {1.0f, 1.0f, 1.0f, 1.0f};
	float const fWhite[4]   = {1.0f, 1.0f, 1.0f, 0.7f};
	float const fYellow[4]  = {1.0f, 1.0f, 0.0f, 0.7f};
	float const fCyan[4]    = {0.0f, 1.0f, 1.0f, 0.7f};
	float const fRed[4]     = {1.0f, 0.0f, 0.0f, 1.0f};
	float const fRedish[4]  = {0.6f, 0.2f, 0.4f, 1.0f};
	float const fBluish[4]  = {0.3f, 0.7f, 1.0f, 0.7f};

	TFixedResourceName sMemUnit = "(KiB)";
	uint32 nUsedMem = nUsedMemInByte >> 10;

	if (nUsedMem > 1024)
	{
		sMemUnit = "(MiB)";
		nUsedMem = nUsedMemInByte >> 20;
	}

	pRenderer->Draw2dLabel(xpos, ypos, 1, fColor, false, "Total Buffers: %d, Buffer Commands: %d Budget Ratio: %.2f", m_soundBuffers.size(), m_BufferCommands.size(), m_fBudgetRatio);
	ypos += 10.0f;
	
	pRenderer->Draw2dLabel(xpos, ypos, 1, fColor, false, "Used Memory: %d %s", nUsedMem, sMemUnit.c_str());
	ypos += 10.0f;

	SoundBufferCRCMapItorConst ItEnd = m_soundBuffers.end();
	for (SoundBufferCRCMapItorConst It=m_soundBuffers.begin(); It!=ItEnd; ++It)
	{
		CSoundBuffer* pBuf = It->second;
		enumBufferType const eBufferType = pBuf->GetProps()->eBufferType;

		ptParamINT32 oParam(0);
		int32 nResult = 0; // TODO: Have a look at the network and micro buffers as they will get rendered "redish" instead of "red"!
		pBuf->GetParam(apSTATE, &oParam);
		oParam.GetValue(nResult);

		if (nResult != 0)
		{
			memcpy(fColor,fWhite,4*sizeof(float));

			if (pBuf->GetProps()->nPrecacheFlags & FLAG_SOUND_PRECACHE_STAY_IN_MEMORY)
				memcpy(fColor,fCyan,4*sizeof(float));

			if (eBufferType == btSOUNDFSB)
				memcpy(fColor,fBluish,4*sizeof(float));

			if (eBufferType == btSAMPLE)
				memcpy(fColor,fYellow,4*sizeof(float));

			if (!pBuf->Loaded(false))
				memcpy(fColor,fRed,4*sizeof(float));
			else
				fColor[3] = min(1.0f, AssetStillNeeded(pBuf) + 0.3f);
		}
		else
		{
			// The buffer is bad.
			memcpy(fColor, fRedish, 4 * sizeof(float));
		}

		if (DrawMode == amdiALLBUFFERS || 
			 (DrawMode == amdiONLYEVENTBUFFERS &&  eBufferType == btEVENT) || 
			 (DrawMode == amdiONLYSAMPLEBUFFERS && (eBufferType == btSAMPLE) || (eBufferType == btSOUNDFSB)))
		{
			stack_string oTemp;
			int32 nNumSimple = 0;
			ptParamINT32 ptTemp(nNumSimple);
			
			if (pBuf->GetParam(apNUMSIMPLESOUNDS, &ptTemp))
			{
				ptTemp.GetValue(nNumSimple);
			}

			if (eBufferType == btEVENT)
			{
				CSoundBufferFmodEx400Event const* const pBufferEvent = static_cast<CSoundBufferFmodEx400Event*>(pBuf);

				if (nNumSimple > 0)
					oTemp.Format("%s Mem: %dKiB Simple: %d Events: %d Ref: %d", pBuf->GetName(), pBuf->GetMemoryUsed() >> 10, nNumSimple, static_cast<int>(pBufferEvent->GetCountEvents()), pBuf->m_nRef);
				else
					oTemp.Format("%s Mem: %dKiB Events: %d Ref: %d", pBuf->GetName(), pBuf->GetMemoryUsed() >> 10, static_cast<int>(pBufferEvent->GetCountEvents()), pBuf->m_nRef);
			}
			else
			{
				if (nNumSimple > 0)
					oTemp.Format("%s Mem: %dKiB Ref: %d Simple: %d", pBuf->GetName(), pBuf->GetMemoryUsed() >> 10, nNumSimple, pBuf->m_nRef);
				else
					oTemp.Format("%s Mem: %dKiB Ref: %d", pBuf->GetName(), pBuf->GetMemoryUsed() >> 10, pBuf->m_nRef);
			}

			pRenderer->Draw2dLabel(xpos, ypos, 1, fColor, false, oTemp);
			ypos += 10.0f;
		}
	} //it
#endif // INCLUDE_AUDIO_PRODUCTION_CODE
}


//////////////////////////////////////////////////////////////////////////
// Asset Management
//////////////////////////////////////////////////////////////////////////

// creates a new SoundBuffer for a referred sound on a specific platform and returns the pointer to it
CSoundBuffer* CSoundAssetManager::CreateSoundBuffer(const SSoundBufferProps &BufferProps, CCryNameCRC const& rBufferCRC)
{
	if (m_pSoundSystem->g_nDebugSound == SOUNDSYSTEM_DEBUG_SIMPLE)
		m_pSoundSystem->Log(eSLT_Message, "<Sound> AssetManager: Create new Buffer %s ", BufferProps.sName.c_str());

	CSoundBuffer* pBuf = m_pSoundSystem->GetIAudioDevice()->CreateSoundBuffer(BufferProps);

	if (pBuf)
		m_soundBuffers[rBufferCRC] = pBuf;
	else
		m_pSoundSystem->Log(eSLT_Warning, "<Sound> AssetManager: Could not create %s!", BufferProps.sName.c_str());

	return pBuf;
}

// gets a sound buffer through its CRC key
// checks and returns a Ptr to a buffer
CSoundBuffer* const CSoundAssetManager::GetSoundBuffer(CCryNameCRC const& rBufferCRC) const
{
	SoundBufferCRCMapItorConst It = m_soundBuffers.find(rBufferCRC);
	
	if (It != m_soundBuffers.end())
		return (It->second);

	return NULL;
}

// removes a SoundBuffer from the AssetManager
bool CSoundAssetManager::RemoveSoundBufferCRC(CCryNameCRC const& rBufferCRC)
{
	SoundBufferCRCMapItor itor = m_soundBuffers.find(rBufferCRC);

	if (itor != m_soundBuffers.end())
	{
		CSoundBuffer *pBuf = (itor->second);

		if (m_pSoundSystem->g_nDebugSound == SOUNDSYSTEM_DEBUG_DETAIL)
			m_pSoundSystem->Log(eSLT_Message, "<Sound> AssetManager: Removing sound [%s]", itor->second->GetName());

		m_soundBuffers.erase(itor);
		return true;
	}

	return false;
}

// locks the SoundBuffers so they don't get unloaded
void CSoundAssetManager::LockResources()
{
	m_lockedResources.resize(0);
	m_lockedResources.reserve(m_soundBuffers.size());

	SoundBufferCRCMapItorConst ItEnd = m_soundBuffers.end();
	for (SoundBufferCRCMapItorConst It=m_soundBuffers.begin(); It!=ItEnd; ++It)
	{			
		CSoundBuffer *pBuf = It->second;
		m_lockedResources.push_back( pBuf );
	}
}

// unlocks the SoundBuffers so they could be unload if needed
void CSoundAssetManager::UnlockResources()
{
	m_lockedResources.clear();
}

// unloads ALL SoundBuffers from the AssetManager
void CSoundAssetManager::UnloadAllSoundBuffers()
{
	//GUARD_HEAP;

	// first iterate through all buffers and unload events to prevent dialog event access invalid memory
	SoundBufferCRCMapItorConst IterEnd = m_soundBuffers.end();
	for (SoundBufferCRCMapItorConst Iter=m_soundBuffers.begin(); Iter!=IterEnd; ++Iter)
	{
		//m_pSoundSystem->GetSystem()->GetILog()->LogToFile("Removing sound [%s]", itor->first.sName.c_str());
		CSoundBuffer *pBuf = (Iter->second);
		if (pBuf && m_pSoundSystem->g_nUnloadData && pBuf->GetProps()->eBufferType == btEVENT)
		{
			const bool bResult1 = pBuf->UnloadData(sbUNLOAD_ALL_DATA_NOW);
			pBuf->GetProps()->nProjectIndex = PROJECTINDEX_INVALID;
			assert(bResult1 && !pBuf->Loaded(false));
		}
	}

	// all other buffers 2nd iteration
	for (SoundBufferCRCMapItorConst Iter=m_soundBuffers.begin(); Iter!=IterEnd; ++Iter)
	{
		//m_pSoundSystem->GetSystem()->GetILog()->LogToFile("Removing sound [%s]", itor->first.sName.c_str());
		CSoundBuffer *pBuf = (Iter->second);
		if (pBuf && m_pSoundSystem->g_nUnloadData && pBuf->GetProps()->eBufferType != btEVENT)
		{
			const bool bResult2 = pBuf->UnloadData(sbUNLOAD_ALL_DATA_NOW);
			pBuf->GetProps()->nProjectIndex = PROJECTINDEX_INVALID;
			assert(bResult2 && !pBuf->Loaded(false));
		}
	}

	// remove all buffer so the clean up does not happen in the next level
	m_soundBuffers.clear();

	// Also make sure to remove pending unload requests (dangling pointer access)
	m_BufferCommands.clear();

	// leave in for debugging
	//for (SoundBufferPropsMapItorConst Iter=m_soundBuffers.begin(); Iter!=IterEnd; ++Iter)
	//{
	//	CSoundBuffer *pBuf = (Iter->second);
	//	if (pBuf && (pBuf->GetAssetHandle() || pBuf->Loading()))
	//	{
	//		assert(0);
	//		int a = 0; // catch me
	//	}
	//}



}

// removes project dependency
void CSoundAssetManager::RemoveProjectDependency()
{
	SoundBufferCRCMapItorConst IterEnd = m_soundBuffers.end();
	for (SoundBufferCRCMapItorConst Iter=m_soundBuffers.begin(); Iter!=IterEnd; ++Iter)
	{
		//m_pSoundSystem->GetSystem()->GetILog()->LogToFile("Removing sound [%s]", itor->first.sName.c_str());
		CSoundBuffer *pBuf = (Iter->second);
		if (pBuf) 
		{
			pBuf->GetProps()->nProjectIndex = PROJECTINDEX_INVALID;

			if (pBuf->GetProps()->eBufferType == btEVENT)
				pBuf->SetAssetHandle(NULL, pBuf->GetProps()->eBufferType);

			//assert(!pBuf->Loaded(false));
		}
	}
}

// informs about project availability
void CSoundAssetManager::ProjectWasLoaded(const ProjectIndex nProjectIndex)
{
	SoundBufferCRCMapItorConst IterEnd = m_soundBuffers.end();
	for (SoundBufferCRCMapItorConst Iter=m_soundBuffers.begin(); Iter!=IterEnd; ++Iter)
	{
		CSoundBuffer *pBuf = (Iter->second);
		if (pBuf && (pBuf->GetProps()->nProjectIndex == nProjectIndex))
			pBuf->Load(NULL);
	}
}

float CSoundAssetManager::AssetStillNeeded(CSoundBuffer* pBuf)
{
	int nNumReferencedSounds = 0;
	ptParamINT32 TempParam(nNumReferencedSounds);
	pBuf->GetParam(apNUMREFERENCEDSOUNDS, &TempParam);
	TempParam.GetValue(nNumReferencedSounds);

	if (nNumReferencedSounds)
		return (1.0f);

	if (m_pSoundSystem->g_nUnloadData == 0)
		return (1.0f);

	if (pBuf->GetProps()->nPrecacheFlags & FLAG_SOUND_PRECACHE_STAY_IN_MEMORY && pBuf->Loaded(false))
		return (1.0f);

	if (pBuf->GetProps()->eBufferType == btSOUNDFSB)
		return (1.0f);

	if (pBuf->GetProps()->nPrecacheFlags & FLAG_SOUND_PRECACHE_UNLOAD_NOW)
		return (0.0f);

	if (pBuf->Loading())
		return (1.0f);

	CTimeValue tDeltaTime = gEnv->pTimer->GetFrameStartTime() - pBuf->GetTimer();
	
	// right now that would be sqrt(nTimesUsed) plus some minimal of 10, that divided through the
	// seconds of that last Update (+1 to rule out div(0)), and that multiplied by the squared number of references to the Buffer
	//float fCacheThreshold = ((sqrt((float)pBuf->GetInfo()->nTimesUsed)+20) / (1.0f + tDeltaTime.GetSeconds()));
	
	uint32 nUsedOverTime = 0;
	if (pBuf->GetInfo()->nTimesUsed > pBuf->GetInfo()->nTimedOut)
		nUsedOverTime = pBuf->GetInfo()->nTimesUsed - pBuf->GetInfo()->nTimedOut;
	
	float fCacheThreshold = nUsedOverTime / (1.0f + (tDeltaTime.GetSeconds()*tDeltaTime.GetSeconds() ));

	return (max(0.0f, fCacheThreshold - 1.0f));
}

//////////////////////////////////////////////////////////////////////////
void CSoundAssetManager::AddBufferCommand(SBufferCommand const& rBufferCommand)
{
  // Don't add different commands for the same buffer, rather update it.
  bool bBufferFound = false;

  tvecBufferCommands::const_iterator const IterEnd(m_BufferCommands.end());

  for (tvecBufferCommands::iterator Iter(m_BufferCommands.begin()); Iter != IterEnd; ++Iter)
  {
    SBufferCommand& rTempBufferCommand = (*Iter);

    if (rTempBufferCommand.pBuffer == rBufferCommand.pBuffer)
    {
      // Buffer is already being worked on so just update it.
			// Make sure to not update nBegin though as that will "rewind" the position that we deleted last
			// and start over again which means we never reach other event data.
      rTempBufferCommand.bRemoveBuffer = rBufferCommand.bRemoveBuffer;
      rTempBufferCommand.eUnloadOption = rBufferCommand.eUnloadOption;

      bBufferFound = true;

      break;
    }
  }

  if (!bBufferFound)
  {
    m_BufferCommands.push_back(rBufferCommand);
  }
}

//////////////////////////////////////////////////////////////////////////
// Information
//////////////////////////////////////////////////////////////////////////

// Gets and Sets Parameter defined in the enumAssetParam list
//bool CSoundAssetManager::GetParam(enumAssetManagerParamSemantics eSemantics, ptParam* pParam)
//{
//}
//
//bool CSoundAssetManager::SetParam(enumAssetManagerParamSemantics eSemantics, ptParam* pParam)
//{
//}

#include UNIQUE_VIRTUAL_WRAPPER(ISoundAssetManager)
